#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>

using namespace std;

// forn : for ( i en [0,n) )
#define forn( i,n ) for ( int i=0; i<(int)(n); i++ )
// fornin : for ( i en [desde,hasta) )
#define fornin(i, desde, hasta) for ( int i=(desde); i<(int)(hasta); i++ )

int cantidad;
/**  TRIE  **/
// Diccionario
char toChar[10] = {'0','1','2','3','4','5','6','7','8','9'};

// Estructura para el nodo.
struct nodoTrie{
    int id; // Nombre
    bool esHoja; // Para ver si el nodo es hoja del trie
    string bill; // El bill asociado a la clave
    int cantHijos;// Cantidad de hijos del Nodo
    struct nodoTrie *hijos[10]; // Los hijos    
};
typedef struct nodoTrie trie; // Facilito la lectura
 
// Inicializa un Nodo en general.
void inicializarNodo(trie &T, int id){
    
    for(int i = 0; i < 10; i++){   
		(T.hijos[i]) = NULL; // No tiene hijos
    }
    T.id = id;
    T.esHoja = true; //Cuando se crea por el momento es hoja
    T.cantHijos = 0;
}

// Arma la solucion.
void armarSolucion(trie &T, string clave){
    if (T.id != -1){
		clave.push_back(toChar[T.id]);
    }
    if (T.esHoja){
		if (T.bill != "invalid"){
			printf("%s %s\n", clave.c_str(), T.bill.c_str()); 
		}
    } else {
		for (int i = 0; i < 10; i++){
			if ((T.hijos[i]) != NULL){
				armarSolucion(*(T.hijos[i]), clave); // Paso el id del nodo actual recursivamente para mostrarlo en la hoja
				delete T.hijos[i];
			}
		}
	}
}

//Verifica si el nodo del trie ya está definido.
bool existeNodo(int id, trie &T){
    return ((T.hijos[id]) != NULL);
}

// Verfifica si hay algún hijo - No se usa cantHijos porque esto es mas rapido
bool hayHijo(trie &T){
    return !T.esHoja;
}

// Verifica si todos los hijos de un nodo son hojas - prec: todos los hijos estan definidos o sea cantHijos = 10
bool todasHojas(trie &T){
    forn(i,10){
		if(! (*(T.hijos[i])).esHoja) return false;
    }
    return true;
}

// Si hay alguna hoja completa el resto de las mismas rescursivamente, sino fija como hoja el nodo actual.
int completarHojas(trie &T, string &valor){
    
    bool hayAlgunHijo = hayHijo(T);
    int i;
    int res = 0;
    
    if(T.cantHijos == 10 && todasHojas(T)){ // Si son todas hojas no puedo agregar mas
		return 0;
    }
    if(hayAlgunHijo){
		for(i = 0; i < 10; i++){
			if ((T.hijos[i]) == NULL){
				trie *nuevo = new trie;
				inicializarNodo(*nuevo, i);
				(*nuevo).bill = valor;
				T.esHoja = false;
				(T.hijos[i]) = nuevo;
				T.cantHijos++;
				if (valor != "invalid"){
					res++;
				}	
			} else{
				 // LLamada recursiva a los que no son hojas
				res += completarHojas(*(T.hijos[i]), valor);	
			}
		}
    }
    return res;
}



//Inserta una clave en un trie - 
//Devuelve cuantas claves inserto a partir de ella incluyendola
int insertar(int clave[], string &valor, trie &T, int i,  int N){
    
    int id = clave[i];
    
    if(i == N - 1){
		if(!existeNodo(id, T)){
			trie *nuevo = new trie;
			inicializarNodo(*nuevo, id);
			(T.hijos[id]) = nuevo;
			T.esHoja = false;
			T.cantHijos++;	
			(*(T.hijos[id])).bill = valor;
			if (valor == "invalid"){
				return 0;
			} else{
				return 1;
			}    
		} else{
			return completarHojas(*(T.hijos[id]), valor);
		}
	}
    
    if(!existeNodo(id, T)){	
		trie *nuevo = new trie;
		inicializarNodo(*nuevo, id);
		T.esHoja = false;
		T.cantHijos++;
		(T.hijos[id]) = nuevo;
    }			
    
    return insertar(clave, valor, *(T.hijos[id]), i+1, N); 
}

// Determina si una clave ya esta o hay algun prefijo de ella
bool yaEstaOHayPrefijo(int clave[], trie &T, int i,  int N){
    
    if(i == N-1){
		if(!existeNodo(clave[i], T)){
			// Si no esta definido un nodo de esa clave no esta
			return false; 
		}else{
			if (hayHijo(*(T.hijos[clave[i]]))){
				if ((*(T.hijos[clave[i]])).cantHijos == 10){
					if(todasHojas(*(T.hijos[clave[i]]))){
						// No puedo agregar nada mas, por ende ya esta
						return true; 
					} else{
						// Puedo agregar claves    		    
						return false; 
					}
				} else{
					// Puedo agregar claves
					return false; 
				}
			} else{
				// Si no hay hijos  ya esta definida
				return true; 
			}
		}
    } else{    
		if(!existeNodo(clave[i], T)){
			// Si no esta un nodo de la clave, la clave no esta
			return false; 
		} else{
			if ((*T.hijos[clave[i]]).esHoja){
				// Si llegue a la hoja, antes del final de la clave, había un prefijo
				return true; 
			} else{
				// Llamada recursiva
				return yaEstaOHayPrefijo(clave, *(T.hijos[clave[i]]), i+1,N); 
			}
		}		
    }
}

// Si no esta definida la clave o algún prefijo de ella inserta la clave.
int insertarSiPuedo(int clave[], string &valor, trie &T, int N){
    
    if(!yaEstaOHayPrefijo(clave, T, 0, N)){
		int cantidad_actual = insertar(clave, valor, T, 0, N);
		cantidad += cantidad_actual;
		return cantidad_actual;
    }
    return 0;
} 

// Agrupa todas las claves que estén de más para que la solucion sea minimal, en 1.
void limpiarSolucion(trie &t, int &cantidad){
    if(!t.esHoja){
		bool todos_iguales = true;
	   
		forn(hijo, 9) {
			if( t.hijos[hijo] == NULL || t.hijos[hijo+1]== NULL ||
				!(t.hijos[hijo]->esHoja) || !(t.hijos[hijo]->esHoja) ||
				t.hijos[hijo]->bill != t.hijos[hijo+1]->bill) {
				todos_iguales = false;
				break;
			}
		}
		if(!todos_iguales){
			forn(n, 10){
				if(t.hijos[n] != NULL) limpiarSolucion(*(t.hijos[n]), cantidad);
			}
		}
		
		todos_iguales = true;
		
		forn(hijo, 9){
			if( t.hijos[hijo] == NULL || t.hijos[hijo+1]== NULL ||
				!(t.hijos[hijo]->esHoja) || !(t.hijos[hijo]->esHoja) ||
				t.hijos[hijo]->bill != t.hijos[hijo+1]->bill) {
				todos_iguales = false;
				break;
			}
		}
    
		if(todos_iguales && t.id != -1){
			t.esHoja = true;
			t.bill = t.hijos[0]->bill;
			t.cantHijos = 0;
			forn(n, 10){
				delete t.hijos[n];
				t.hijos[n] = NULL;
			}
			cantidad -= 9;
		}
	}
}

/**  FIN TRIE  **/

// Determina que claves enviar a insertar, dado un determinado rango.
void GuardarDatosDe(string plan, int desde[], int hasta[], int tam, int ya_verificados, trie &t){
    bool es_rango_completo = true;
    fornin(n, ya_verificados, tam){
        if(desde[n] != 0 || hasta[n] != 9){
            es_rango_completo = false;
            break;
        }
    }
    // Si estoy en el rango x000 a x999 inserto x
    if(es_rango_completo) {
		// Caso 000...0 999..9
       if(ya_verificados == 0){
		   int desde2[1];
		   forn(n, 10){
			   desde2[0] = n;
			   insertarSiPuedo(desde2, plan, t, 1);
		   }
       } else {
			int desde2[ya_verificados];
			forn(n, ya_verificados) desde2[n] = desde[n];
			insertarSiPuedo(desde2, plan, t, ya_verificados);
			return;
		}
    }
	// Si ya vi todos menos el ultimo inserto todos
    if(ya_verificados == tam - 1) {
        fornin(x, desde[ya_verificados], hasta[ya_verificados]+1){
            desde[ya_verificados] = x;
            insertarSiPuedo(desde, plan, t, tam);
        }
        return;
    }
	// Si son distintos
    if(desde[ya_verificados] != hasta[ya_verificados]) {
        int desde2[tam];
        int hasta2[tam];
        forn(x, ya_verificados) {
            desde2[x] = desde[x];
            hasta2[x] = hasta[x];
        }
        fornin(n, desde[ya_verificados], hasta[ya_verificados] + 1) {
            desde2[ya_verificados] = n;
            hasta2[ya_verificados] = n;
            fornin(x, ya_verificados+1, tam){
                if(n == desde[ya_verificados]) {
                    desde2[x] = desde[x];
                } else {
                    desde2[x] = 0;
				}
                if(n == hasta[ya_verificados]){
                    hasta2[x] = hasta[x];
				} else {
                    hasta2[x] = 9;
				}
            }
			GuardarDatosDe(plan, desde2, hasta2, tam, ya_verificados+1, t);
        }
    } else {
        GuardarDatosDe(plan, desde, hasta, tam, ya_verificados + 1, t);
    }
}

// Convierte los datos del input.
void GuardarDatosDe(string plan, string desde, string hasta, trie &t) {

    int desde_int[desde.size()];
    int hasta_int[desde.size()];

    forn(i, desde.size()) {
        desde_int[i] = (int)desde[i] - 48;
        hasta_int[i] = (int)desde[i] - 48;
    }

    forn(i, hasta.size()) {
        hasta_int[desde.size() - i - 1] = (int) hasta[hasta.size() - i - 1] - 48;
	}

    GuardarDatosDe(plan, desde_int, hasta_int, desde.size(), 0, t);
}

int main(){
    int cantida_lista_original = 0;
    bool ciclo = false;  // Para mostrar un salto de linea entre cada resultado menos despues del ultimo
    string vacio;
    while(cin >> cantida_lista_original) {
		trie inicial;
		string caracter;
		cantidad = 0;
		inicializarNodo(inicial, -1);
		forn(i, cantida_lista_original) {
			string desde, hasta;
			string plan;
			cin >> desde >> caracter >> hasta >> plan;
			GuardarDatosDe(plan, desde, hasta, inicial);
		}
		limpiarSolucion(inicial, cantidad);
		if (ciclo){
			printf("\n");
		}
		ciclo = true;
		printf("%d\n", cantidad);
		armarSolucion(inicial, vacio);
    }
    return 0;
}
